package util.excel;

import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.NumberToTextConverter;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import util.cast.CastUtil;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * excel 工具类
 * @Author zt
 * @date 2021/11/11 13:52
 * @Version 1.0
 */
public class ExcelUtil {

    /** 时间格式：默认为yyyy-MM-dd */
    private String DATE_PATTERN = "yyyy-MM-dd";

    /** 图片宽度，默认为：100 */
    private int IMAGE_WIDTH = 30;

    /** 图片高度，默认为：50 */
    private int IMAGE_HEIGHT = 5;

    /** 单元格的最大宽度 */
    private int[] maxWidth;

    /**
     * 单页支持最多数据列：超过65534会出错
     * 若数据列多余65534则需要通过MORE_EXCEL_FLAG、MORE_SHEET_FLAG来区别生成多个Excel、还是sheet
     */
    private int maxRowCount = 2500;

    /** 大量数据，多个Excel标识---0001 */
    private String  MORE_EXCEL_FLAG = "0001";

    /** 大量数据，多个sheet标识---0001 */
    private String MORE_SHEET_FLAG = "0002";

    /**
     * 默认构造函数
     */
    public ExcelUtil(){
    }

    /**
     * 读取excel内容
     *      * <p>
     *      * 用户模式下：
     *      * 弊端：对于少量的数据可以，单数对于大量的数据，会造成内存占据过大，有时候会造成内存溢出
     *      * 建议修改成事件模式
     * @param filePath 文件路径
     * @return 结果
     * @throws Exception 异常
     */
    public static List<Map<String, String>> redExcel(String filePath) throws Exception {
        File file = new File(filePath);
        if (!file.exists()){
            throw new Exception("文件不存在");
        }
        InputStream in = new FileInputStream(file);

        // 读取整个Excel
        XSSFWorkbook sheets = new XSSFWorkbook(in);
        // 获取第一个表单Sheet
        XSSFSheet sheetAt = sheets.getSheetAt(0);
        ArrayList<Map<String, String>> list = new ArrayList<>();

        //默认第一行为标题行，i = 0
        XSSFRow titleRow = sheetAt.getRow(0);
        // 循环获取每一行数据
        for (int i = 1; i < sheetAt.getPhysicalNumberOfRows(); i++) {
            XSSFRow row = sheetAt.getRow(i);
            LinkedHashMap<String, String> map = new LinkedHashMap<>();
            // 读取每一格内容
            for (int index = 0; index < row.getPhysicalNumberOfCells(); index++) {
                XSSFCell titleCell = titleRow.getCell(index);
                XSSFCell cell = row.getCell(index);
                // cell.setCellType(XSSFCell.CELL_TYPE_STRING); 过期，使用下面替换
                cell.setCellType(CellType.STRING);
                if (cell.getStringCellValue().equals("")) {
                    continue;
                }
                map.put(getString(titleCell), getString(cell));
            }
            if (map.isEmpty()) {
                continue;
            }
            list.add(map);
        }
        return list;
    }

    /**
     * 把单元格的内容转为字符串
     *
     * @param xssfCell 单元格
     * @return String
     */
    public static String getString(XSSFCell xssfCell) {
        if (xssfCell == null) {
            return "";
        }
        if (xssfCell.getCellTypeEnum() == CellType.NUMERIC) {
            return String.valueOf(xssfCell.getNumericCellValue());
        } else if (xssfCell.getCellTypeEnum() == CellType.BOOLEAN) {
            return String.valueOf(xssfCell.getBooleanCellValue());
        } else {
            return xssfCell.getStringCellValue();
        }
    }

    /**
     * @param datePattern 指定的时间格式
     */
    public ExcelUtil(String datePattern){
        this.DATE_PATTERN = datePattern;
    }

    /**
     * @param imageWidth 指定图片的宽度
     * @param imageHeight 指定图片的高度
     *
     */
    public ExcelUtil(int imageWidth,int imageHeight){
        this.IMAGE_HEIGHT = imageHeight;
        this.IMAGE_WIDTH = imageWidth;
    }

    /**
     * @param datePatter 指定时间格式
     * @param imageWidth 指定图片的宽度
     * @param imageHeight 指定图片的高度
     */
    public ExcelUtil(String datePatter,int imageWidth,int imageHeight){
        this.DATE_PATTERN = datePatter;
        this.IMAGE_HEIGHT = imageHeight;
        this.IMAGE_WIDTH = imageWidth;
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,如有图片请转换为byte[]<br>
     * header、excelList规则如下：<br>
     * header、excelList中的Bean必须对应（javaBean的属性顺序）：如下<br>
     * header：姓名、年龄、性别、班级<br>
     * Bean：name、age、sex、class<br>
     *
     * @param header 表格属性列名数组
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean  属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @return 生成的HSSFWorkBook
     */
    public HSSFWorkbook exportExcel(String[] header, List<Object> excelList, String sheetTitle){
        //生成一个Excel
        HSSFWorkbook book = new HSSFWorkbook();
        //生成一个表格
        sheetTitle = getSheetTitle(sheetTitle);   //判断、设置sheetTitle
        HSSFSheet sheet = book.createSheet(sheetTitle);

        //设置Excel里面数据
        setExcelContentData(book,sheet,header,excelList);

        System.out.println("——————————————————ExcelExportHelper:Excel生成成功...");

        return book;
    }

    /**
     *
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,如有图片请转换为byte[]<br>
     * header、properties需要一一对应：<Br>
     * header = ["学号","年龄","性别","班级"]
     * properties = ["id","age","sex","class"],其对应的excelList中javaBean的属性值
     *
     * @param header Excel表头
     * @param properties 表头对应javaBean中的属性
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @return 生成的HSSFWorkbook
     */
    public HSSFWorkbook exportExcel(String[] header,String[] properties,List<Object> excelList,
                                    String sheetTitle){
        //生成一个Excel
        HSSFWorkbook book = new HSSFWorkbook();
        // 生成一个表格
        sheetTitle = getSheetTitle(sheetTitle); // 判断、设置sheetTitle
        HSSFSheet sheet = book.createSheet(sheetTitle);

        // 设置Excel里面数据
        setExcelContentData(book, sheet, header, properties ,excelList);

        System.out.println("——————————————————ExcelExportHelper:Excel生成成功...");

        return book;
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将Excel保存至某个路径下,
     * 如有图片请转换为byte[]<br>
     * header、excelList规则如下：<br>
     * header、excelList中的Bean必须一一对应(javaBean的属性顺序)：如下<br>
     * header：姓名、年龄、性别、班级<br>
     * Bean：name、age、sex、class<br>
     *
     * @param header 表格属性列名数组
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param filePath Excel文件保存位置
     * @param fileName Excel文件名
     *
     */
    public void exportExcelAndSave(String[] header,List<Object> excelList,String sheetTitle,
                                   String filePath,String fileName){
        //生成Excel
        HSSFWorkbook book = exportExcel(header, excelList, sheetTitle);

        //保存生成的Excel
        saveExcel(book,filePath,fileName);
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将Excel保存至某个路径下,
     * 如有图片请转换为byte[]<br>
     * header、properties需要一一对应：<Br>
     * header = ["学号","年龄","性别","班级"]<Br>
     * properties = ["id","age","sex","class"],其对应的excelList中javaBean的属性值
     *
     * @param header 表格属性列名数组
     * @param properties 表头对应javaBean中的属性
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的  javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param filePath Excel文件保存位置
     * @param fileName Excel文件名
     */
    public void exportExcelAndSave(String[] header,String[] properties,List<Object> excelList,String sheetTitle,
                                   String filePath,String fileName){
        //生成Excel
        HSSFWorkbook book = exportExcel(header, properties,excelList, sheetTitle);
        //保存生成的Excel
        saveExcel(book,filePath,fileName);
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将 Excel 打包 zip 格式保存至某个路径下,
     * 如有图片请转换为byte[]<br>
     * header、excelList规则如下：<br>
     * header、excelList中的Bean必须一一对应(javaBean的属性顺序)：如下<br>
     * header：姓名、年龄、性别、班级<br>
     * Bean：name、age、sex、class<br>
     *
     *
     * @param header 表格属性列名数组
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的  javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param filePath zip文件保存位置
     * @param excelName Excel名称
     * @param zipName zip名称
     *
     */
    public void exportExcelAndZip(String[] header,List<Object> excelList,String sheetTitle,
                                  String filePath,String excelName,String zipName){
        //生成Excel
        HSSFWorkbook book = exportExcel(header, excelList, sheetTitle);

        //将生成的Excel打包保存起来
        List<HSSFWorkbook> books = new ArrayList<HSSFWorkbook>();
        books.add(book);
        zipExcelAndSave(books, filePath, zipName, excelName);
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将 Excel 打包 zip 格式保存至某个路径下,
     * 如有图片请转换为byte[]<br>
     * header、properties需要一一对应：<Br>
     * header = ["学号","年龄","性别","班级"]
     * properties = ["id","age","sex","class"],其对应的excelList中javaBean的属性值
     *
     * @param header 表格属性列名数组
     * @param properties 表头对应javaBean的属性
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle  表格标题名
     * @param filePath zip文件保存位置
     * @param excelName Excel名称
     * @param zipName zip名称
     *
     */
    public void exportExcelAndZip(String[] header,String[] properties,List<Object> excelList,String sheetTitle,
                                  String filePath,String excelName,String zipName){
        //生成Excel
        HSSFWorkbook book = exportExcel(header, properties,excelList, sheetTitle);

        //将生成的Excel打包保存起来
        List<HSSFWorkbook> books = new ArrayList<HSSFWorkbook>();
        books.add(book);
        zipExcelAndSave(books, filePath, zipName, excelName);
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,如有图片请转换为byte[]<br>
     * 用于大数据量时使用,涉及到一个表只能有65536行,当数据量较大时会直接写入下一个表(excel、sheet)
     * header、excelList规则如下：<br>
     * header、excelList中的Bean必须一一对应(javaBean的属性顺序)：如下<br>
     * header：姓名、年龄、性别、班级<br>
     * Bean：name、age、sex、class<br>
     *
     * @param header 表格属性列名数组
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的  javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param flag 分页标识为。flag == 0001：生成多个Excel,flag == 0002：生成多个sheet
     *
     *
     * @return 结果
     */
    public List<HSSFWorkbook> exportExcelForBigData(String[] header,List<Object> excelList,String sheetTitle,
                                                    String flag){
        List<HSSFWorkbook> list = new ArrayList<>();    //创建表数据结果集

        //判断需要生成几个Excel
        int num  = excelList.size() % maxRowCount == 0 ? excelList.size() / maxRowCount : excelList.size() / maxRowCount + 1;

        HSSFWorkbook book = new HSSFWorkbook();
        List<Object> newList  = null;    //新数据列表
        String newTitle = null;    //新title
        for(int i = 0 ; i < num ; i++){
            //计算新的数据列表
            int beginRowNum = maxRowCount * i;
            int endRowNum = maxRowCount * (i + 1) > excelList.size() ? excelList.size() : maxRowCount * (i + 1);
            newList = excelList.subList(beginRowNum, endRowNum);
            newTitle = getSheetTitle(sheetTitle) + "_" + i;
            if(MORE_EXCEL_FLAG.equals(flag)){     //如果是创建多个Excel
                book = exportExcel(header, newList, newTitle);
                list.add(book);
            }
            else if(MORE_SHEET_FLAG.equals(flag)){   //创建多sheet
                HSSFSheet sheet = book.createSheet(newTitle);
                setExcelContentData(book,sheet,header,newList);
            }
        }

        if(MORE_SHEET_FLAG.equals(flag)){   //创建多sheet
            list.add(book);
        }

        return list;
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,如有图片请转换为byte[]<br>
     * 用于大数据量时使用,涉及到一个表只能有65536行,当数据量较大时会直接写入下一个表(excel、sheet)
     * header、properties需要一一对应：<Br>
     * header = ["学号","年龄","性别","班级"]
     * properties = ["id","age","sex","class"],其对应的excelList中javaBean的属性值
     *
     * @param header 表格属性列名数组
     * @param properties 表头对应javaBean的属性
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param flag 分页标识为。flag == 0001：生成多个Excel,flag == 0002：生成多个sheet
     *
     * @return 结果
     */
    public List<HSSFWorkbook> exportExcelForBigData(String[] header,String[] properties,
                                                    List<Object> excelList,String sheetTitle, String flag){
        List<HSSFWorkbook> list = new ArrayList<>();    //创建表数据结果集
        // 判断需要生成几个Excel
        int num = excelList.size() % maxRowCount == 0 ? excelList.size() / maxRowCount : excelList.size() / maxRowCount + 1;

        HSSFWorkbook book = new HSSFWorkbook();
        List<Object> newList = null; // 新数据列表
        String newTitle = null; // 新title
        for (int i = 0; i < num; i++) {
            // 计算新的数据列表
            int beginRowNum = maxRowCount * i;
            int endRowNum = maxRowCount * (i + 1) > excelList.size() ? excelList.size() : maxRowCount * (i + 1);
            newList = excelList.subList(beginRowNum, endRowNum);
            newTitle = getSheetTitle(sheetTitle) + "_" + i;
            if (MORE_EXCEL_FLAG.equals(flag)) { // 如果是创建多个Excel
                book = exportExcel(header,properties, newList, newTitle);
                list.add(book);
            } else if (MORE_SHEET_FLAG.equals(flag)) { // 创建多sheet
                HSSFSheet sheet = book.createSheet(newTitle);
                setExcelContentData(book, sheet, header, properties,newList);
            }
        }

        if (MORE_SHEET_FLAG.equals(flag)) { // 创建多sheet
            list.add(book);
        }
        return list;
    }


    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将Excel保存至某个路径下,
     * 如有图片请转换为byte[]<br>
     * 用于大数据量时使用,涉及到一个表只能有65536行,当数据量较大时会直接写入下一个表(excel、sheet)
     * header、excelList规则如下：<br>
     * header、excelList中的Bean必须一一对应(javaBean的属性顺序)：如下<br>
     * header：姓名、年龄、性别、班级<br>
     * Bean：name、age、sex、class<br>
     *
     * @param header 表格属性列名数组
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param flag 分页标识为。flag == 0001：生成多个Excel,flag == 0002：生成多个sheet
     * @param filePath 文件保存路径
     * @param fileName 保存文件名
     *
     */
    public void exportExcelForBigDataAndSave(String[] header,List<Object> excelList,String sheetTitle,
                                             String flag,String filePath,String fileName){
        //获取数据结果集
        List<HSSFWorkbook> books = exportExcelForBigData(header, excelList, sheetTitle, flag);
        String _fileName = "";
        for(int i = 0 ; i < books.size() ; i ++){
            HSSFWorkbook book = books.get(i);
            _fileName = getFileName(fileName) + "_0" + i;
            //保存Excel文件
            saveExcel(book, filePath, _fileName);
        }
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将Excel保存至某个路径下,
     * 如有图片请转换为byte[]<br>
     * 用于大数据量时使用,涉及到一个表只能有65536行,当数据量较大时会直接写入下一个表(excel、sheet)
     * header、properties需要一一对应：<Br>
     * header = ["学号","年龄","性别","班级"]
     * properties = ["id","age","sex","class"],其对应的excelList中javaBean的属性值
     *
     * @param header 表格属性列名数组
     * @param properties 表头对应javaBean属性
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的  javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param flag 分页标识为。flag == 0001：生成多个Excel,flag == 0002：生成多个sheet
     * @param filePath 文件保存路径
     * @param fileName 保存文件名
     *
     */
    public void exportExcelForBigDataAndSave(String[] header,String[] properties,List<Object> excelList,String sheetTitle,
                                             String flag,String filePath,String fileName){
        //获取数据结果集
        List<HSSFWorkbook> books = exportExcelForBigData(header, properties,excelList, sheetTitle, flag);

        String _fileName = "";
        for(int i = 0 ; i < books.size() ; i ++){
            HSSFWorkbook book = books.get(i);
            _fileName = getFileName(fileName) + "_0" + i;
            //保存Excel文件
            saveExcel(book, filePath, _fileName);
        }
    }


    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将 Excel 打包成 ZIP
     * 保存至某个路径下,如有图片请转换为byte[]<br>
     * 用于大数据量时使用,涉及到一个表只能有65536行,当数据量较大时会直接写入下一个表(excel、sheet)
     * header、excelList规则如下：<br>
     * header、excelList中的Bean必须一一对应(javaBean的属性顺序)：如下<br>
     * header：姓名、年龄、性别、班级<br>
     * Bean：name、age、sex、class<br>
     *
     * @param header 表格属性列名数组
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param flag 分页标识为。flag == 0001：生成多个Excel,flag == 0002：生成多个sheet
     * @param filePath 文件保存路径
     * @param excelName Excel文件名
     * @param zipName zip文件名
     *
     */
    public void exportExcelForBigDataAndZipAndSave(String[] header,List<Object> excelList,String sheetTitle,
                                                   String flag,String filePath,String excelName,String zipName){
        //获取生成的Excel集合
        List<HSSFWorkbook> books = exportExcelForBigData(header, excelList, sheetTitle, flag);

        //将生成的Excel打包并保存
        zipExcelAndSave(books, filePath, zipName, excelName);
    }

    /**
     * 通用方法，使用 java 反射机制，根据提供表头 header ，数据列 excelList 生成 Excel,并将 Excel 打包成 ZIP
     * 保存至某个路径下,如有图片请转换为byte[]<br>
     * 用于大数据量时使用,涉及到一个表只能有65536行,当数据量较大时会直接写入下一个表(excel、sheet)
     * header、properties需要一一对应：<Br>
     * header = ["学号","年龄","性别","班级"]
     * properties = ["id","age","sex","class"],其对应的excelList中javaBean的属性值
     * @param header     表格属性列名数组
     * @param properties 表头对应javaBean属性
     * @param excelList 需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的 javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
     * @param sheetTitle 表格标题名
     * @param flag 分页标识为。 flag == 0001：生成多个Excel,flag == 0002：生成多个sheet
     * @param filePath 文件保存路径
     * @param excelName Excel文件名
     * @param zipName ZIP文件名
     */
    public void exportExcelForBigDataAndZipAndSave(String[] header,String[] properties,List<Object> excelList,String sheetTitle,
                                                   String flag,String filePath,String excelName,String zipName){
        //获取生成的Excel集合
        List<HSSFWorkbook> books = exportExcelForBigData(header, properties,excelList, sheetTitle, flag);

        //将生成的Excel打包并保存
        zipExcelAndSave(books, filePath, zipName, excelName);
    }

    /**
     * 填充Excel数据内容
     * @param book Excel
     * @param sheet sheet
     * @param header Excel头部title
     * @param excelList Excel数据列
     */
    @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
    private void setExcelContentData(HSSFWorkbook book,HSSFSheet sheet,String[] header,List<Object> excelList) {
        //设置列头样式(居中、变粗、蓝色)
        HSSFCellStyle headerStyle = book.createCellStyle();
        setHeaderStyle(headerStyle, book);

        // 设置单元格样式
        HSSFCellStyle cellStyle = book.createCellStyle();
        setCellStyle(cellStyle, book);

        // 创建头部
        HSSFRow row = createHeader(sheet, headerStyle, header);

        // 画图的顶级管理器，一个sheet只能获取一个（一定要注意这点）
        HSSFPatriarch patriarch = sheet.createDrawingPatriarch();


        int index = 0;
        /* 避免在迭代过程中产生的新对象太多，这里讲循环内部变量全部移出来 */
        Object t = null;
        HSSFCell cell = null;
        Field field = null;
        String fieldName = null;
        String getMethodName = null;
        Class tCls = null;
        Method getMethod = null;
        Object value = null;
        // 遍历集合数据，产生数据行
        Iterator<Object> it = excelList.iterator();
        maxWidth = new int[header.length];   //初始化单元格宽度
        while (it.hasNext()) {
            index++;
            row = sheet.createRow(index);
            // 设置数据列
            t = it.next();
            // 利用反射，根据javabean属性的先后顺序，动态调用getXxx()方法得到属性值
            Field[] fields = t.getClass().getDeclaredFields();
            for (short i = 0; i < fields.length; i++) {
                cell = row.createCell(i);
                cell.setCellStyle(cellStyle);
                field = fields[i];
                fieldName = field.getName();
                getMethodName = "get"+ fieldName.substring(0, 1).toUpperCase()+ fieldName.substring(1);  //构建getter方法
                try {
                    tCls = t.getClass();
                    getMethod = tCls.getMethod(getMethodName,new Class[] {});
                    value = (Object) getMethod.invoke(t, new Object[] {});
                    // 将value设置当单元格指定位置
                    setCellData(row, index, i, value, cell, sheet, patriarch, book);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                } catch (SecurityException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                }
            }
        }

        System.out.println("-------------------------填充Excel数据成功..........");
    }

    /**
     * 填充Excel内容
     * @param book Excel
     * @param sheet sheet
     * @param header Excel头部title
     * @param properties 表头对应javaBean属性
     * @param excelList 数据
     */
    @SuppressWarnings("rawtypes")
    private void setExcelContentData(HSSFWorkbook book, HSSFSheet sheet, String[] header, String[] properties,
                                     List<Object> excelList) {
        //设置列头样式(居中、变粗、蓝色)
        HSSFCellStyle headerStyle = book.createCellStyle();
        setHeaderStyle(headerStyle, book);

        // 设置单元格样式
        HSSFCellStyle cellStyle = book.createCellStyle();
        setCellStyle(cellStyle, book);

        // 创建头部
        HSSFRow row = createHeader(sheet, headerStyle, header);

        // 画图的顶级管理器，一个sheet只能获取一个（一定要注意这点）
        HSSFPatriarch patriarch = sheet.createDrawingPatriarch();

        /* 为了避免迭代过程中产生过多的新对象，这里将循环内部变量全部移出来 */
        int index = 0;
        Object t = null;
        HSSFCell cell = null;
        Object o = null;
        Class clazz = null;
        PropertyDescriptor pd = null;
        Method getMethod = null;
        // 遍历集合数据，产生数据行
        Iterator<Object> it = excelList.iterator();
        maxWidth = new int[header.length];   //初始化单元格宽度
        while (it.hasNext()) {
            index++;
            row = sheet.createRow(index);
            // 设置数据列
            t = it.next();
            for(int i = 0 ; i < header.length ; i++){
                cell = row.createCell(i);
                cell.setCellStyle(cellStyle);
                o = null;    //每一个单元格都需要将O设置为null
                try {
                    clazz = t.getClass();
                    pd = new PropertyDescriptor(properties[i],clazz);
                    getMethod = pd.getReadMethod();   // 获得get方法
                    if (pd != null) {
                        o  = getMethod.invoke(t);   //执行get方法返回一个Object
                    }
                    setCellData(row, index, i, o, cell, sheet, patriarch, book);
                } catch (IntrospectionException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                    System.out.println("——————————————————创建Excel数据列表时出错。method:setDataRow,message："+e.getMessage());
                }
            }
        }

        System.out.println("——————————————————填充Excel数据成功..........");
    }


    /**
     * 设置sheet的title，若为空则为yyyyMMddHH24mmss
     * @param sheetTitle 格式
     * @return 结果
     */
    private  String getSheetTitle(String sheetTitle) {
        String title = null;
        if(sheetTitle != null && !"".equals(sheetTitle)){
            title = sheetTitle;
        }
        else{
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH24mmss");
            title = sdf.format(date);
        }
        return title;
    }


    /**
     *  设置Excel图片的格式：字体居中、变粗、蓝色、12号
     * @param headerStyle  头部样式
     * @param book 生产的excel book 	 HSSFWorkbook对象
     */
    private void setHeaderStyle(HSSFCellStyle headerStyle,HSSFWorkbook book) {
        headerStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);   //水平居中
        headerStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
        //设置字体
        HSSFFont font = book.createFont();
        font.setFontHeightInPoints((short) 12);     //字号：12号
        font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);   //变粗
        font.setColor(HSSFColor.BLUE.index);   //蓝色

        headerStyle.setFont(font);
    }


    /**
     * 设置单元格样式
     * @param cellStyle 单元格样式
     * @param book book HSSFWorkbook对象
     */
    private void setCellStyle(HSSFCellStyle cellStyle, HSSFWorkbook book) {
        cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);   //水平居中
        cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中

        HSSFFont font = book.createFont();
        font.setFontHeightInPoints((short)12);

        cellStyle.setFont(font);
    }


    /**
     * 根据头部样式、头部数据创建Excel头部
     * @param sheet sheet
     * @param headerStyle 头部样式
     * @param header 头部数据
     * @return 结果
     */
    private HSSFRow createHeader(HSSFSheet sheet,HSSFCellStyle headerStyle,
                                 String[] header) {
        HSSFRow headRow = sheet.createRow(0);
        headRow.setHeightInPoints((short)(20));   //设置头部高度
        //添加数据
        HSSFCell cell = null;
        for(int i = 0 ; i < header.length ; i++){
            cell = headRow.createCell(i);
            cell.setCellStyle(headerStyle);
            HSSFRichTextString text = new HSSFRichTextString(header[i]);
            cell.setCellValue(text);
        }

        return headRow;
    }


    /**
     *  设置单元格数据
     * @param row 指定行
     * @param index 列
     * @param i 行数
     * @param value 单元格值 cellValue
     * @param cell 单元格 HSSFCell对象
     * @param sheet 	sheet HSSFSheet对象
     * @param patriarch 顶级画板 用于实现突破
     * @param book Excel HSSFWorkbook对象
     */
    private void setCellData(HSSFRow row, int index ,int i ,Object value,HSSFCell cell,HSSFSheet sheet,HSSFPatriarch patriarch,HSSFWorkbook book) {
        String textValue = null;
        if (value instanceof Date) {    //为日期设置时间格式
            Date date = (Date) value;
            SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN);
            textValue = sdf.format(date);
        }
        if(value instanceof byte[]){   //byte为图片
            //设置图片单元格宽度、高度
            row.setHeightInPoints((short)(IMAGE_HEIGHT * 10));
            sheet.setColumnWidth(i, IMAGE_WIDTH * 256);
            HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 1023, 255,(short) i, index, (short) i, index);
            anchor.setAnchorType(3);
            //插入图片
            byte[] bsValue = (byte[]) value;
            patriarch.createPicture(anchor, book.addPicture(bsValue, HSSFWorkbook.PICTURE_TYPE_JPEG));
        }
        else{   //其余全部当做字符处理
            if(value != null){
                textValue = String.valueOf(value);
            }
            else{
                textValue = "";
            }
        }
        // 如果不是图片数据，就利用正则表达式判断textValue是否全部由数字组成
        if (textValue != null) {
            Pattern p = Pattern.compile("^//d+(//.//d+)?$");
            Matcher matcher = p.matcher(textValue);

            //设置单元格宽度，是文字能够全部显示
            setCellMaxWidth(textValue,i);
            sheet.setColumnWidth(i, maxWidth[i]);    //设置单元格宽度
            row.setHeightInPoints((short)(20));   //设置单元格高度
            if (matcher.matches()) {
                // 是数字当作double处理
                cell.setCellValue(Double.parseDouble(textValue));
            } else {
                cell.setCellValue(textValue);
            }
        }
    }

    /**
     * 获取文件名，若为空，则规则为：yyyyMMddHH24mmss+6位随机数
     * @param fileName 文件名
     * @return 结果
     */
    private String getFileName(String fileName) {
        if(fileName == null || "".equals(fileName)){
            //日期
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH24mmss");
            //随机数
            Random random = new Random();
            fileName = sdf.format(date) + String.valueOf(Math.abs(random.nextInt() * 1000000));
        }
        return fileName;
    }


    /**
     * 根据字数来获取单元格大小,并更新当前列的最大宽度
     * @param textValue 源数据
     * @param i 指定列
     */
    private void setCellMaxWidth(String textValue,int i ) {
        int size = textValue.length();
        int width = (size + 6) * 256;
        if(maxWidth[i] <= width){
            maxWidth[i] = width;
        }
    }


    /**
     * 将生成的Excel保存到指定路径下
     * @param book 生成的Excel HSSFWorkbook对象
     * @param filePath 需要保存的路劲
     * @param fileName Excel文件名
     */
    private void saveExcel(HSSFWorkbook book, String filePath, String fileName) {
        //检测保存路劲是否存在，不存在则新建
        checkFilePathIsExist(filePath);
        //将Excel保存至指定目录下
        fileName = getFileName(fileName);
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(filePath + "\\" + fileName + ".xls");
            book.write(out);
            System.out.println("——————————————————保存Excel文件成功，保存路径：" + filePath + "\\" + fileName + ".xls");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("——————————————————保存Excel文件失败。exportExcelForListAndSave,message："+e.getMessage());
        }finally{
            if(out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 将生成的Excel打包并保存到指定路径下
     * @param books 生成的Excel HSSFWorkbook list集合
     * @param filePath 保存路劲
     * @param zipName zip 文件名
     * @param excelName Excel文件名
     */
    private void zipExcelAndSave(List<HSSFWorkbook> books,String filePath,String zipName,String excelName){
        //检测保存路径是否存在，若不存在则新建
        checkFilePathIsExist(filePath);

        zipName = getFileName(zipName);
        excelName = getFileName(excelName);

        //将Excel打包并保存至指定目录下
        FileOutputStream out = null;
        ZipOutputStream zip = null;
        try {
            out = new FileOutputStream(filePath + "\\" + zipName + ".zip");
            zip = new ZipOutputStream(out);
            HSSFWorkbook book = null;
            String _excelName = "";
            for (int i = 0; i < books.size(); i++) {
                book = books.get(i);
                _excelName = getFileName(excelName) + "_0" + i;
                ZipEntry entry = new ZipEntry(_excelName + ".xls");
                zip.putNextEntry(entry);
                book.write(zip);
            }
            System.out.println("——————————————————保存Excel文件成功，保存路径：" + filePath + "\\" + zipName + ".xls");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("——————————————————保存Excel文件失败。method:exportExcelForBigDataAndSave,message：" + e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("——————————————————保存Excel文件失败。method:exportExcelForBigDataAndSave,message：" + e.getMessage());
        } finally {
            if (zip != null) {
                try {
                    zip.flush();
                    zip.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (out != null) {
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 检测保存路径是否存在，不存在则新建
     * @param filePath 文件路径
     */
    private void checkFilePathIsExist(String filePath) {
        File file = new File(filePath);

        if(!file.exists()){
            file.mkdirs();
        }
    }


    /**
     * 解析Excel 支持2003、2007<br>
     * 利用反射技术完成propertis到obj对象的映射，并将相对应的值利用相对应setter方法设置到obj对象中最后add到list集合中<br>
     * properties、obj需要符合如下规则：<br>
     * 1、obj对象必须存在默认构造函数，且属性需存在setter方法<br>
     * 2、properties中的值必须是在obj中存在的属性，且obj中必须存在这些属性的setter方法。<br>
     * 3、properties中值得顺序要与Excel中列相相应，否则值会设置错：<br>
     * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;excel:编号    姓名         年龄       性别<br>
     * properties:id  name  age  sex<br>
     *
     * @param file 待解析的Excel文件
     * @param properties 与Excel相对应的属性
     * @param obj 反射对象的Class
     * @return 结果
     * @throws Exception 异常
     */
    @SuppressWarnings("rawtypes")
    public static List<Object> excelRead(File file,String[] properties,Class obj) throws Exception{
        Workbook book = null;
        try {
            book = new XSSFWorkbook(new FileInputStream(file));     //解析2003
        } catch (Exception e) {
            book = new HSSFWorkbook(new FileInputStream(file));      //解析2007
        }

        return getExcelContent(book,properties,obj);
    }

    /**
     * 解析Excel 支持2003、2007<br>
     * 利用反射技术完成propertis到obj对象的映射，并将相对应的值利用相对应setter方法设置到obj对象中最后add到list集合中<br>
     * properties、obj需要符合如下规则：<br>
     * 1、obj对象必须存在默认构造函数，且属性需存在setter方法<br>
     * 2、properties中的值必须是在obj中存在的属性，且obj中必须存在这些属性的setter方法。<br>
     * 3、properties中值得顺序要与Excel中列相相应，否则值会设置错：<br>
     * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;excel：编号    姓名         年龄       性别<br>
     * properties：id  name  age  sex<br>
     *
     * @param filePath 待解析的Excel文件的路径
     * @param properties 与Excel相对应的属性
     * @param obj 反射对象的Class
     * @return 结果
     * @throws Exception 异常
     */
    @SuppressWarnings("rawtypes")
    public static List<Object> excelRead(String filePath,String[] properties,Class obj) throws Exception{
        File file = new File(filePath);
        if(!file.exists()){
            throw new Exception("指定的文件不存在");
        }
        return excelRead(file, properties, obj);
    }

    /**
     * 根据params、object解析Excel，并且构建list集合
     *
     * @param book WorkBook对象，他代表了待将解析的Excel文件
     * @param properties 需要参考Object的属性
     * @param obj 构建的Object对象，每一个row都相当于一个object对象
     * @return 结果
     * @throws Exception 异常
     */
    @SuppressWarnings("rawtypes")
    private static List<Object> getExcelContent(Workbook book, String[] properties,
                                                Class obj) throws Exception {
        List<Object> resultList = new ArrayList<Object>();        //初始化结果解
        Map<String, Method> methodMap = getObjectSetterMethod(obj);
        Map<String, Field> fieldMap = getObjectField(obj);
        for(int numSheet = 0 ; numSheet < book.getNumberOfSheets(); numSheet++){
            Sheet sheet = book.getSheetAt(numSheet);
            if(sheet == null){   //谨防中间空一行
                continue;
            }

            for(int numRow = 1 ; numRow < sheet.getLastRowNum() ; numRow++){   //一个row就相当于一个Object
                Row row = sheet.getRow(numRow);
                if(row == null){
                    continue;
                }
                resultList.add(getObject(row,properties,methodMap,fieldMap,obj));
            }
        }
        return resultList;
    }

    /**
     * 获取row的数据，利用反射机制构建Object对象
     *
     * @param row row对象
     * @param properties Object参考的属性
     * @param methodMap object对象的setter方法映射
     * @param fieldMap object对象的属性映射
     * @return 结果
     * @throws Exception 异常
     */
    @SuppressWarnings("rawtypes")
    private static Object getObject(Row row, String[] properties,
                                    Map<String, Method> methodMap,Map<String, Field> fieldMap,Class obj) throws Exception {
        Object object = obj.newInstance();
        for(int numCell = 0 ; numCell < row.getLastCellNum() ; numCell++){
            Cell cell = row.getCell(numCell);
            if(cell == null){
                continue;
            }
            String cellValue = getValue(cell);
            String property = properties[numCell].toLowerCase();
            Field field = fieldMap.get(property);    //该property在object对象中对应的属性
            Method method = methodMap.get(property);  //该property在object对象中对应的setter方法
            setObjectPropertyValue(object,field,method,cellValue);
        }
        return object;
    }

    /**
     * 根据指定属性的的setter方法给object对象设置值
     *
     * @param obj object对象
     * @param field object对象的属性
     * @param method object对象属性的相对应的方法
     * @param value 需要设置的值
     * @throws Exception 异常
     */
    private static void setObjectPropertyValue(Object obj, Field field,
                                               Method method, String value) throws Exception {
        Object[] oo = new Object[1];

        String type = field.getType().getName();
        if ("java.lang.String".equals(type) || "String".equals(type)) {
            oo[0] = value;
        } else if ("java.lang.Integer".equals(type) || "java.lang.int".equals(type) || "Integer".equals(type) || "int".equals(type)) {
            if (value.length() > 0)
                oo[0] = Integer.valueOf(value);
        } else if ("java.lang.Float".equals(type) || "java.lang.float".equals(type)  || "Float".equals(type) || "float".equals(type)) {
            if (value.length() > 0)
                oo[0] = Float.valueOf(value);
        } else if ("java.lang.Double".equals(type)  || "java.lang.double".equals(type) || "Double".equals(type) || "double".equals(type)) {
            if (value.length() > 0)
                oo[0] = Double.valueOf(value);
        } else if ("java.math.BigDecimal".equals(type)  || "BigDecimal".equals(type)) {
            if (value.length() > 0)
                oo[0] = new BigDecimal(value);
        } else if ("java.util.Date".equals(type)  || "Date".equals(type)) {
            if (value.length() > 0){//当长度为19(yyyy-MM-dd HH24:mm:ss)或者为14(yyyyMMddHH24mmss)时Date格式转换为yyyyMMddHH24mmss
                if(value.length() == 19 || value.length() == 14){
                    oo[0] = CastUtil.strToDate(value,"yyyyMMddHH24mmss");
                }
                else{     //其余全部转换为yyyyMMdd格式
                    oo[0] = CastUtil.strToDate(value, "yyyyMMdd");
                }
            }
        } else if ("java.sql.Timestamp".equals(type)) {
            if (value.length() > 0)
                oo[0] = CastUtil.strToDate(value, "yyyyMMddHH24mmss");
        } else if ("java.lang.Boolean".equals(type)  || "Boolean".equals(type)) {
            if (value.length() > 0)
                oo[0] = Boolean.valueOf(value);
        } else if ("java.lang.Long".equals(type) || "java.lang.long".equals(type)  || "Long".equals(type) || "long".equals(type)) {
            if (value.length() > 0)
                oo[0] = Long.valueOf(value);
        }
        try {
            method.invoke(obj, oo);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    @SuppressWarnings("static-access")
    private static String getValue(Cell cell) {
        if (cell.getCellType() == cell.CELL_TYPE_BOOLEAN) {
            return String.valueOf(cell.getBooleanCellValue());
        } else if (cell.getCellType() == cell.CELL_TYPE_NUMERIC) {
            return NumberToTextConverter.toText(cell.getNumericCellValue());
        } else {
            return String.valueOf(cell.getStringCellValue());
        }
    }

    /**
     * 获取object对象所有属性的Setter方法，并构建map对象，结构为Map<'field','method'>
     * @param object object对象
     * @return 结果
     */
    @SuppressWarnings("rawtypes")
    private static Map<String, Method> getObjectSetterMethod(Class object) {
        Field[] fields = object.getDeclaredFields();       //获取object对象的所有属性
        Method[] methods = object.getDeclaredMethods();    //获取object对象的所有方法
        Map<String, Method> methodMap = new HashMap<String, Method>();
        for(Field field : fields){
            String attri = field.getName();
            for(Method method : methods){
                String meth = method.getName();
                //匹配set方法
                if(meth != null && "set".equals(meth.substring(0, 3)) &&
                        Modifier.isPublic(method.getModifiers()) &&
                        ("set"+Character.toUpperCase(attri.charAt(0))+attri.substring(1)).equals(meth)){
                    methodMap.put(attri.toLowerCase(), method);       //将匹配的setter方法加入map对象中
                    break;
                }
            }
        }

        return methodMap;
    }

    /**
     * 获取object对象的所有属性，并构建map对象，对象结果为Map<'field','field'>
     * @param object object对象
     * @return 结果
     */
    @SuppressWarnings("rawtypes")
    private static Map<String, Field> getObjectField(Class object) {
        Field[] fields = object.getDeclaredFields();       //获取object对象的所有属性
        Map<String, Field> fieldMap = new HashMap<String,Field>();
        for(Field field : fields){
            String attri = field.getName();
            fieldMap.put(attri.toLowerCase(), field);
        }
        return fieldMap;
    }
}
